/**
 * Disk Workload
 */
#include <stdio.h>
#include <fcntl.h>
#include <pthread.h>
#include "disk.h"
#include "random.h"
#include "cJSON.h"
#include "resource_model.h"
#include "scheduler.h"
#include "generator.h"

#define PERMS 0666
#define WRITE_BLOCK_SIZE 1024
#define READ_BLOCK_SIZE 1024

mode_definition_list *disk_modes[HASHSIZE];

char * setup_file(cJSON *);
void create_disk_workload(void *);

mode * setup_read_random(cJSON *);
mode * setup_write_random(cJSON *);
mode * setup_read_sequential(cJSON *);
mode * setup_write_sequential(cJSON *);

int read_bytes(int fd, long pos, int n)
{
  if (lseek(fd, pos, 0) >= 0) {
    int steps = n / READ_BLOCK_SIZE; /* Read in standard block size */
    int runoff = n % READ_BLOCK_SIZE;
    int i, result = 0;
    char *data = malloc(READ_BLOCK_SIZE);
    for(i = 0; i<= steps; i++) {
      int bytes = (i == steps) ? runoff : WRITE_BLOCK_SIZE;
      if( bytes > 0) {
        result += read(fd,data,bytes);
      }
    }
    free(data);
    return result;
  }
  else {
    return -1;
  }
}

int write_bytes(int fd, long pos, int n)
{
  if (lseek(fd, pos, 0) >= 0) {
    int steps = n / WRITE_BLOCK_SIZE; /* Write in standard block size */
    int runoff = n % WRITE_BLOCK_SIZE;
    int i;
    int result = 0;
    char * data = malloc(WRITE_BLOCK_SIZE);
    for(i = 0; i <= steps; i++) {
      int bytes = (i == steps) ? runoff : WRITE_BLOCK_SIZE;
      if( bytes > 0) {
        result += write(fd,data,bytes);
      }
    }
    free(data);
    return result;
  }
  else {
    return -1;
  }
}

resource_model *setup_disk(cJSON *json) {
  //Setup all of our modes
  install_mode(disk_modes,"wr",setup_write_random);
  install_mode(disk_modes,"rr",setup_read_random);
  install_mode(disk_modes,"rs",setup_read_sequential);
  install_mode(disk_modes,"ws",setup_write_sequential);
  //Setup the interarrival rate
  cJSON *distribution = cJSON_GetObjectItem(json,"distribution");
  setup_rng("disk_arrival", distribution);
  //Set up the File
  char *filepath = setup_file(json);
  //Build the model
  resource_model *model = malloc(sizeof(resource_model));
  model->birth = create_disk_workload;
  model->state = (void *) filepath;
  model->modes = initialize_modes(disk_modes, json);
  model->cnt_modes = count_modes(json);
  return model;
}

char *setup_file(cJSON *json) {
  cJSON *filesize = cJSON_GetObjectItem(json, "filesize");
  unsigned long i_filesize = filesize->valueint * 1000;
  char **buf = malloc(sizeof(char *));
  asprintf(buf, "./disk/%lu", (unsigned long int)pthread_self()); /* Create a filename from the thread id */
  creat(*buf,0666);
  int file = open(*buf,O_WRONLY,0);
  write_bytes(file, 0, i_filesize);
  close(file);
  char **uniform = malloc(sizeof(char *));
  asprintf(uniform, "{\"type\":\"uniform\",\"min\":0,\"max\":%lu}", i_filesize);
  setup_rng("random_position_in_file",cJSON_Parse(*uniform));
  return *buf;
}

void create_disk_workload(void *args) {
  resource_model *model = (resource_model *)args;
  double next_arrival_ms = get_random_number("disk_arrival");
  event *next_birth = malloc(sizeof(event));
  if(next_birth == NULL) {
    exit(1);
  }
  mode *m = select_mode(model);
  next_birth->timestamp = ms_from_now(next_arrival_ms);  
  next_birth->job = create_disk_workload;
  next_birth->args = args;
  next_birth->next = NULL;
  pthread_t *thread = malloc(sizeof(pthread_t));
  if(thread == NULL) {
    exit(1);
  }
  pthread_create(thread, NULL,m->function, model->state);
  pthread_detach(*thread);
  schedule_event(next_birth);
}

void *read_rand(void *args) {
  char *filepath = (char *)args;
  int fd = open(filepath,O_RDONLY,0);
  setup_rng_buffer(pthread_self());
  unsigned long position = 0;
  int bytes_to_read = (int) (get_random_number("rr_length") * 1000);
  int i = 0;
  for(i; i < bytes_to_read; i++){
    position = get_random_number("random_position_in_file");
    read_bytes(fd, position, 1);
  }
  close(fd);
  remove_rng_buffer(pthread_self());
}

void *write_rand(void *args) {
  char *filepath = (char *)args;
  int fd = open(filepath,O_WRONLY,0);
  setup_rng_buffer(pthread_self());
  unsigned long bytes_to_write = (unsigned long) (get_random_number("wr_length") * 1000);
  unsigned long position = 0;
  int i = 0;
  for(i; i < bytes_to_write; i++){
    position = get_random_number("random_position_in_file");
    write_bytes(fd, position, 1);
  }
  close(fd);
  remove_rng_buffer(pthread_self());
}

void *read_sequential(void *args) {
  char *filepath = (char *)args;
  int fd = open(filepath, O_RDONLY, 0);
  setup_rng_buffer(pthread_self());
  unsigned long position = get_random_number("random_position_in_file");
  unsigned long bytes_to_read = (unsigned long) (get_random_number("rs_length") * 1000);
  read_bytes(fd, position, bytes_to_read);
  close(fd);
  remove_rng_buffer(pthread_self());
}

void *write_sequential(void *args) {
  char *filepath = (char *)args;
  int fd = open(filepath, O_WRONLY, 0);
  setup_rng_buffer(pthread_self());
  unsigned long position = get_random_number("random_position_in_file");
  unsigned long bytes_to_write = (unsigned long) (get_random_number("ws_length") * 1000);
  write_bytes(fd, position, bytes_to_write);
  close(fd);
  remove_rng_buffer(pthread_self());
}

mode *setup_read_random(cJSON *json) {
  cJSON *json_probability = cJSON_GetObjectItem(json,"p");
  setup_rng("rr_length", cJSON_GetObjectItem(json,"length"));
  mode *m = malloc(sizeof(mode));
  m->key = "rr";
  m->function = read_rand;
  m->probability = json_probability->valuedouble;
  return m;
}

mode *setup_write_random(cJSON *json) {
  cJSON *json_probability = cJSON_GetObjectItem(json,"p");
  setup_rng("wr_length", cJSON_GetObjectItem(json,"length"));
  mode *m = malloc(sizeof(mode));
  m->key = "wr";
  m->function = write_rand;
  m->probability = json_probability->valuedouble;
  return m;
}

mode *setup_read_sequential(cJSON *json) {
  cJSON *json_probability = cJSON_GetObjectItem(json,"p");
  setup_rng("rs_length", cJSON_GetObjectItem(json,"length"));
  mode *m = malloc(sizeof(mode));
  m->key = "rs";
  m->function = read_sequential;
  m->probability = json_probability->valuedouble;
  return m;
}

mode *setup_write_sequential(cJSON *json) {
  cJSON *json_probability = cJSON_GetObjectItem(json,"p");
  setup_rng("ws_length", cJSON_GetObjectItem(json,"length"));
  mode *m = malloc(sizeof(mode));
  m->key = "ws";
  m->function = write_sequential;
  m->probability = json_probability->valuedouble;
  return m;
}
